/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package proyectotsm;

import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

/**
 * Clase Compresor.java
 *
 * @author Srgank
 * @author JuanCarlos
 * @author Gabriel
 */
public class Compresor {

    // Atributos
    private int AltoTesela;
    private int AnchoTesela;
    private int numTeselasX;
    private int numTeselasY;
    private ArrayList<Imagen> imagenes;
    private Collection<Tesela> teselas;
    private ArrayList<Ocurrencia> ocurrencias;
    private int delta;
    private int recur;
    private int rango;

    // Constructor de la clase
    public Compresor(ArrayList<Imagen> imagenes, int anchoteselas, int altoteselas, int parametroCompresion, int recur, int rango) {
        this.AnchoTesela = anchoteselas;
        this.AltoTesela = altoteselas;
        this.imagenes = imagenes;
        this.delta = parametroCompresion;
        teselas = new ArrayList<Tesela>();
        ocurrencias = new ArrayList<Ocurrencia>();
        this.recur = recur;
        this.rango = rango;
    }

    /**
     * Crea todas las teselas, en función del numero en x e y
     */
    public void crearTeselas() {

        for (int i = 0; i < numTeselasX; i++) {
            for (int j = 0; j < numTeselasY; j++) {
                teselas.add(new Tesela(AnchoTesela, AltoTesela, i, j, null));
            }
        }
    }

    /**
     * Configura los parametros de la interfaz para
     * utilizarlos en el momento de la compresion
     */
    public void setearParametros() {
        Iterator i = imagenes.iterator();
        Imagen imgTemp = (Imagen) i.next();

        int ancho = imgTemp.getAncho();
        int alto = imgTemp.getAlto();
        this.numTeselasX = (int) Math.floor(ancho / AnchoTesela);
        this.numTeselasY = (int) Math.floor(alto / AltoTesela);

        System.out.println("ancho:" + ancho + " alto:" + alto + " anchotesela: " + AnchoTesela + " Altotesela: " + AltoTesela + ""
                + " numteselasx: " + numTeselasX + " numteselasy: " + numTeselasY);

        crearTeselas();
    }

    /**
     * Metodo que nos descomprime la imagen comprimida a partir de las
     * ocurrencias y las dimensiones de las teselas
     *
     * @param ocurrencias La lista de ocurrencias cargadas
     * @param anchoteselas La distancia en pixeles del ancho de la tesela
     * @param altoteselas La distancia en pixeles del alto de la tesela
     * @return
     */
    public ArrayList<Imagen> decomprimir(ArrayList<Ocurrencia> ocurrencias, int anchoteselas, int altoteselas) {

        Collections.reverse((List<?>) imagenes);
        Collections.reverse((List<?>) ocurrencias);
        Iterator i = ocurrencias.iterator();
        Iterator m = imagenes.iterator();
        int teselaX, teselaY, posx, posy, num;
        ArrayList<Imagen> imagenesNew = new ArrayList();
        Imagen img1 = (Imagen) m.next();
        BufferedImage imagen_buena = img1.getBufferedImagen();

        Ocurrencia oc = (Ocurrencia) i.next();
        teselaX = oc.getnTeselaX();
        teselaY = oc.getnTeselaY();

        while (m.hasNext()) {
            Imagen img2 = (Imagen) m.next();
            BufferedImage segunda_imagen = img2.getBufferedImagen();
            System.out.println("Descomprimiendp imagen " + img1.getNombre());
            teselaX = oc.getnTeselaX();
            teselaY = oc.getnTeselaY();
            num = oc.getNumero();

            while (num == oc.getNumero() && i.hasNext()) {

                teselaX = oc.getnTeselaX();
                teselaY = oc.getnTeselaY();
                posx = oc.getPosX();
                posy = oc.getPosY();
                num = oc.getNumero();

                System.out.println("La tesela " + teselaX + " " + teselaY + " la compenso en la posicion " + posx + " " + posy);
                BufferedImage tesela = segunda_imagen.getSubimage(posx, posy, AnchoTesela, AltoTesela);
                imagen_buena = recomponerTesela(imagen_buena, tesela, teselaX, teselaY, AnchoTesela, AltoTesela);

                oc = (Ocurrencia) i.next();
            }
            img1.setImagen(imagen_buena);
            imagenesNew.add(img1);
            imagen_buena = segunda_imagen;
            img1 = img2;
        }
        img1.setImagen(imagen_buena);
        imagenesNew.add(img1);
        return imagenesNew;
    }

    /**
     * Nos recompone la imagen de la tesela en la imagen final a devolver
     *
     * @param img La imagen de entrada
     * @param tesela La tesela a efectuar la copia
     * @param nTeselaX El numero de teselas en el eje x
     * @param nTeselaY El numero de teselas en el eje y
     * @param anchotesela El ancho de la tesela en pixeles
     * @param altotesela El alto de la tesela en pixeles
     * @return La imagen recompuesta con la tesela en cuestion
     */
    public BufferedImage recomponerTesela(BufferedImage img, BufferedImage tesela, int nTeselaX, int nTeselaY, int anchotesela, int altotesela) {

        System.out.println("Compensando tesela: " + nTeselaX + " " + nTeselaY);

        WritableRaster wras_tmp = ((WritableRaster) img.getData());
        WritableRaster wras_tmp2 = ((WritableRaster) tesela.getData());
        int pix[] = new int[wras_tmp.getNumBands()];
        int pix2[] = new int[wras_tmp2.getNumBands()];

        for (int i = 0; i < AnchoTesela; i++) {
            for (int j = 0; j < AltoTesela; j++) {

                pix = wras_tmp.getPixel(nTeselaX * AnchoTesela + i, nTeselaY * AltoTesela + j, pix);
                pix2 = wras_tmp2.getPixel(i, j, pix2);

                pix[Imagen.Rchannel] = pix2[Imagen.Rchannel];
                pix[Imagen.Gchannel] = pix2[Imagen.Gchannel];
                pix[Imagen.Bchannel] = pix2[Imagen.Bchannel];

                wras_tmp.setPixel(nTeselaX * AnchoTesela + i, nTeselaY * AltoTesela + j, pix);
            }
        }

        img.setData(wras_tmp);
        return img;
    }

    /**
     * Nos comprime la lista de imagenes cargadas en la clase con los
     * parametros configurados desde la interfaz grafica
     *
     * @return La lista que inclue la lista de imagenes y la lista de ocurrencias
     */
    public ArrayList[] comprimir() {
        int cont = 1;
        Iterator<Imagen> i = imagenes.iterator();
        Imagen imagen_buena = (Imagen) i.next();
        int offset = 0;
        int[][] tOcupadas = new int[imagen_buena.getAncho()][imagen_buena.getAlto()];
        while (i.hasNext()) {
            System.out.println("Imagen numero: " + offset);

            Imagen imagen = i.next();
            Iterator<Tesela> it = teselas.iterator();
            int tesela = 0;
            while (it.hasNext() && (cont % rango != 0)) {
                System.out.println("tesela numero: " + tesela);
                tesela++;
                BufferedImage buffImagen = imagen.getBufferedImagen();

                Tesela t = it.next();

                t.setImage(buffImagen.getSubimage(t.getX() * t.getAncho(), t.getY() * t.getAlto(),
                        t.getAncho(), t.getAlto()));

                buscarTesela(t, imagen_buena, imagen, tOcupadas);
            }
            imagen_buena = imagen;
            tOcupadas = new int[imagen_buena.getAncho()][imagen_buena.getAlto()];

            System.out.println("CONTADOR --------> " + cont);

            cont++;
            offset++;
        }
        System.out.println("ocurrencias:" + ocurrencias.size());
        ArrayList[] returns = new ArrayList[2];
        returns[0] = imagenes;
        returns[1] = ocurrencias;
        return returns;

    }

    /**
     * Busca la tesela dentro de una imagen de entrada
     *
     * @param tesela La tesela a buscar
     * @param imagen La imagen donde buscar
     * @param imagenAmodificar La imagen resultante sin la tesela
     * @param tOcupadas Las teselas que se han consultado
     */
    public void buscarTesela(Tesela tesela, Imagen imagen, Imagen imagenAmodificar, int[][] tOcupadas) {
        BufferedImage img = imagen.getBufferedImagen();
        BufferedImage tes = tesela.getImage();

        int nVueltas = recur;
        int x = tesela.getX() * tesela.getAncho();
        int y = tesela.getY() * tesela.getAlto();

        for (int i = 1; i < nVueltas; i++) {
            for (int xact = x; x < (xact + i); x++) {
                if (x + AnchoTesela < img.getWidth() && y + AltoTesela < img.getHeight() && x >= 0 && y >= 0) {
                    BufferedImage tes2 = img.getSubimage(x, y, AnchoTesela, AltoTesela);

                    if (tOcupadas[x][y] != 1) {
                        if (compararTesela(tes, tes2)) {
                            tesela.setXencontrada(x);
                            tesela.setYencontrada(y);
                            restarTesela(imagen, imagenAmodificar, tesela);

                            System.out.println("Tesela" + tesela.getX() + " " + tesela.getY() + "encontrada en : " + x + " " + y);
                            ocurrencias.add(new Ocurrencia(tesela.getX(), tesela.getY(), x, y, imagenAmodificar.getNumero()));

                            for (int k = x; k < x + tes.getWidth(); k++) {
                                for (int l = y; l < y + tes.getHeight(); l++) {
                                    //System.out.println("K: "+k+" L: "+l);
                                    tOcupadas[k][l] = 1;
                                }
                            }
                            x = imagen.getAncho();
                            y = imagen.getAlto();
                            break;
                        }
                    }
                }
            }

            for (int yact = y; y < (yact + i); y++) {
                if (x + AnchoTesela < img.getWidth() && y + AltoTesela < img.getHeight() && x >= 0 && y >= 0) {
                    BufferedImage tes2 = img.getSubimage(x, y, AnchoTesela, AltoTesela);

                    if (tOcupadas[x][y] != 1) {
                        if (compararTesela(tes, tes2)) {
                            tesela.setXencontrada(x);
                            tesela.setYencontrada(y);
                            restarTesela(imagen, imagenAmodificar, tesela);

                            System.out.println("Tesela" + tesela.getX() + " " + tesela.getY() + "encontrada en : " + x + " " + y);
                            ocurrencias.add(new Ocurrencia(tesela.getX(), tesela.getY(), x, y, imagenAmodificar.getNumero()));


                            for (int k = x; k < x + tes.getWidth(); k++) {
                                for (int l = y; l < y + tes.getHeight(); l++) {
                                    tOcupadas[k][l] = 1;
                                }
                            }
                            x = imagen.getAncho();
                            y = imagen.getAlto();
                            break;
                        }
                    }
                }
            }

            for (int xact = x; x > (xact - i); x--) {
                if (x + AnchoTesela < img.getWidth() && y + AltoTesela < img.getHeight() && x >= 0 && y >= 0) {
                    BufferedImage tes2 = img.getSubimage(x, y, AnchoTesela, AltoTesela);

                    if (tOcupadas[x][y] != 1) {
                        if (compararTesela(tes, tes2)) {
                            tesela.setXencontrada(x);
                            tesela.setYencontrada(y);
                            restarTesela(imagen, imagenAmodificar, tesela);

                            System.out.println("Tesela" + tesela.getX() + " " + tesela.getY() + "encontrada en : " + x + " " + y);
                            ocurrencias.add(new Ocurrencia(tesela.getX(), tesela.getY(), x, y, imagenAmodificar.getNumero()));


                            for (int k = x; k < x + tes.getWidth(); k++) {
                                for (int l = y; l < y + tes.getHeight(); l++) {
                                    tOcupadas[k][l] = 1;
                                }
                            }
                            x = imagen.getAncho();
                            y = imagen.getAlto();
                            break;
                        }
                    }
                }
            }

            for (int yact = y; y > (yact - i); y--) {
                if (x + AnchoTesela < img.getWidth() && y + AltoTesela < img.getHeight() && x >= 0 && y >= 0) {
                    BufferedImage tes2 = img.getSubimage(x, y, AnchoTesela, AltoTesela);

                    if (tOcupadas[x][y] != 1) {
                        if (compararTesela(tes, tes2)) {
                            tesela.setXencontrada(x);
                            tesela.setYencontrada(y);

                            restarTesela(imagen, imagenAmodificar, tesela);
                            System.out.println("Tesela" + tesela.getX() + " " + tesela.getY() + "encontrada en : " + x + " " + y);
                            ocurrencias.add(new Ocurrencia(tesela.getX(), tesela.getY(), x, y, imagenAmodificar.getNumero()));

                            for (int k = x; k < x + tes.getWidth(); k++) {
                                for (int l = y; l < y + tes.getHeight(); l++) {
                                    tOcupadas[k][l] = 1;
                                }
                            }
                            x = imagen.getAncho();
                            y = imagen.getAlto();
                            break;
                        }
                    }
                }
            }
        }
    }

    /**
     * Elimina de la imagen la tesela seleccionada
     *
     * @param imagen La imagen de entrada
     * @param imagenAmodificar La imagen resultante
     * @param tesela La tesela a eliminar
     */
    public void restarTesela(Imagen imagen, Imagen imagenAmodificar, Tesela tesela) {
        int NTeselaX = tesela.getX();
        int NTeselaY = tesela.getY();
        int ocurrenciaX = tesela.getXencontrada();
        int ocurrenciaY = tesela.getYencontrada();
        BufferedImage img1 = imagen.getBufferedImagen();
        BufferedImage img2 = imagenAmodificar.getBufferedImagen();

        WritableRaster wras_tmp = ((WritableRaster) img1.getData());
        WritableRaster wras_tmp2 = ((WritableRaster) img2.getData());
        int pix[] = new int[wras_tmp.getNumBands()];
        int pix2[] = new int[wras_tmp2.getNumBands()];
        int medR = 0;
        int medG = 0;
        int medB = 0;
        for (int i = 0; i < AnchoTesela; i++) {
            for (int j = 0; j < AltoTesela; j++) {
                pix2 = wras_tmp.getPixel(NTeselaX * AnchoTesela + i, NTeselaY * AltoTesela + j, pix2);
                medR += pix2[Imagen.Rchannel];
                medG += pix2[Imagen.Gchannel];
                medB += pix2[Imagen.Bchannel];
            }
        }

        medR = medR / (AltoTesela * AnchoTesela);
        medG = medG / (AltoTesela * AnchoTesela);
        medB = medB / (AltoTesela * AnchoTesela);
        for (int i = 0; i < AnchoTesela; i++) {
            for (int j = 0; j < AltoTesela; j++) {

                pix2 = wras_tmp.getPixel(NTeselaX * AnchoTesela + i, NTeselaY * AltoTesela + j, pix2);
                pix = wras_tmp2.getPixel(ocurrenciaX + i, ocurrenciaY + j, pix);

                pix2[Imagen.Rchannel] = medR;
                pix2[Imagen.Gchannel] = medG;
                pix2[Imagen.Bchannel] = medB;

                wras_tmp2.setPixel(NTeselaX * AnchoTesela + i, NTeselaY * AltoTesela + j, pix2);
            }
        }
        img2.setData(wras_tmp2);
        imagenAmodificar.setImagen(img2);
    }

    /**
     * Metodo de comparacion de teselas, en función de los pixeles o un
     * cierto rango de pixeles, para encontrar su semejanza. El algoritmo se
     * basa en comparar un numero de pixeles, definido por el salto, y decidir
     * si la mayoria de la region se parece. En caso afirmativo, devolvemos un
     * valor verdadero. En caso contrario, paramos la ejecución de la comparacion
     * y retornamos un false
     *
     * @param img1 La tesela de entrada 1
     * @param img2 La tesela de entrada 2
     * @return Si son iguales o no
     */
    public boolean compararTesela(BufferedImage img1, BufferedImage img2) {
        WritableRaster wras_tmp = ((WritableRaster) img1.getData());
        WritableRaster wras_tmp2 = ((WritableRaster) img2.getData());
        int pix1[] = new int[wras_tmp.getNumBands()];
        int pix2[] = new int[wras_tmp2.getNumBands()];

        int suma1 = 0;
        int suma2 = 0;

        /* Parametro margen de comparacion */
        int paso = 1;

        /* Recorremos la tesela para comparar mediante la media */
        for (int i = 0; i < img1.getWidth(); i += paso) {
            for (int j = 0; j < img1.getHeight(); j += paso) {
                pix1 = wras_tmp.getPixel(i, j, pix1);
                pix2 = wras_tmp2.getPixel(i, j, pix2);

                /* Sumamos las tres componentes */
                suma1 = (pix1[0] + pix1[1] + pix1[2]);
                suma2 = (pix2[0] + pix2[1] + pix2[2]);

                /* Comprobamos si cumple la condicion */
                if (Math.abs(suma1 - suma2) >= delta) {
                    return false;
                }
            }
        }
        return true;
    }
}
